/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include <fstream>
#include "securec.h"
#include "ecmascript/ecma_vm.h"
#include "ecmascript/js_runtime_options.h"
#include "ecmascript/log.h"
#include "ecmascript/napi/include/jsnapi.h"

namespace panda::ecmascript {

class ChunkDecoder
{
public:
    ChunkDecoder(std::string fileContent)
    {
        auto u64Ptr = reinterpret_cast<const uint64_t *>(fileContent.c_str());
        size_t currInd = 0;
        heapObjCnt = u64Ptr[currInd++];
        rootObjCnt = u64Ptr[currInd++];
        ShareObjCnt = u64Ptr[currInd++];
        strTableOffset = u64Ptr[currInd++];
        LOG_ECMA(INFO) << "ChunkDecoder init: heapObjCnt=" << heapObjCnt << ", rootObjCnt=" << rootObjCnt
                       << ", ShareObjCnt=" << ShareObjCnt << std::hex << ", strTableOffset=0x" << strTableOffset;
        auto cPtr = fileContent.c_str();
        for (uint64_t i = 0; i < rootObjCnt; i++) {
            rootAddrMap[u64Ptr[currInd++]] = nullptr;
        }
        auto &objInfoVec = rawHeapArgs.rawObjInfoVec;
        auto &objAddrMap = rawHeapArgs.objOldAddrMapNewAddr;
        for (uint64_t i = 0; i < heapObjCnt; ++i) {
            auto objInfo = new RawHeapObjInfo();
            objInfo->tInfo.addr = u64Ptr[currInd];
            objInfo->tInfo.id = u64Ptr[currInd + 1]; // 1 : offset of objId
            objInfo->tInfo.objSize = u64Ptr[currInd + 2]; // 2 : offset of objSize
            objInfo->tInfo.offset = u64Ptr[currInd + 3]; // 3 : offset of obj
            objInfo->tInfo.stringId = u64Ptr[currInd + 4]; // 4 : offset of stringId
            if (rootAddrMap.find(objInfo->tInfo.addr) != rootAddrMap.end()) {
                objInfo->isRoot = true;
                rootAddrMap[objInfo->tInfo.addr] = objInfo;
            } else {
                objInfo->isRoot = false;
            }
            objInfoVec.push_back(objInfo);
            currInd = currInd + sizeof(AddrTableItem) / sizeof(uint64_t);
            auto objMem = new char[objInfo->tInfo.objSize];
            auto ret = memcpy_s(objMem, objInfo->tInfo.objSize, cPtr + objInfo->tInfo.offset, objInfo->tInfo.objSize);
            if (ret != 0) {
                LOG_ECMA(ERROR) << "ChunkDecoder memcpy_s failed, ret=" << ret;
                return;
            }
            objInfo->newAddr = objMem;
            objAddrMap.emplace(objInfo->tInfo.addr, objMem);
        }
        DecodeStrTable(cPtr, fileContent.size());
    }

    ~ChunkDecoder()
    {
        auto &objInfoVec = rawHeapArgs.rawObjInfoVec;
        for (auto obj : objInfoVec) {
            delete[] obj->newAddr;
            delete obj;
        }
        auto &strTableIdMap = rawHeapArgs.strTableIdMapNewStr;
        for (auto obj : strTableIdMap) {
            delete[] obj.second;
        }
    }

    void DecodeStrTable(const char *charPtr, uint64_t fileSize)
    {
        auto currInd = strTableOffset;
        if (currInd >= fileSize) {
            LOG_ECMA(ERROR) << "DecodeStrTable no str table: str=" << charPtr;
            return;
        }
        auto &strTableIdMap = rawHeapArgs.strTableIdMapNewStr;

        auto u64Ptr = reinterpret_cast<const uint64_t *>(charPtr + currInd);
        auto strCnt = *u64Ptr;
        LOG_ECMA(INFO) << "DecodeStrTable: strCnt=" << std::dec << strCnt ;
        while(currInd < fileSize && strTableIdMap.size() < strCnt) {
            auto id = *reinterpret_cast<const uint64_t *>(charPtr + currInd);
            currInd += sizeof(uint64_t);
            if (currInd >= fileSize) {
                break;
            }
            auto *currPtr = &charPtr[currInd];
            auto currSize = strlen(currPtr) + 1;
            if (currSize == 1) {
                currInd += currSize;
                continue;
            }
            auto tmpCharArr = new char[currSize];
            auto ret = memcpy_s(tmpCharArr, currSize, currPtr, currSize);
            if (ret != 0) {
                LOG_ECMA(ERROR) << "DecodeStrTable memcpy_s failed: str=" << currPtr;
                return;
            }
            strTableIdMap[id] = tmpCharArr;
            currInd += currSize;
        }
        LOG_ECMA(INFO) << "DecodeStrTable finished: strTableVec.size=" << strTableIdMap.size();
    }

    RawHeapInfoArgs &GetRawHeapInfoArgs()
    {
        return rawHeapArgs;
    }

private:
    uint64_t heapObjCnt;
    uint64_t rootObjCnt;
    uint64_t ShareObjCnt;
    uint64_t strTableOffset;
    CUnorderedMap<uint64_t, RawHeapObjInfo *> rootAddrMap;
    RawHeapInfoArgs rawHeapArgs;
};

const std::string PC_TOOL_HELP_MSG =
    "Usage: ark_js_heap_snapshot_pc_tool <bin_filename> <ouput_filename>\n"
    "\tWill decode the heapdump bin file to the target file named ouput_filename\n";


std::string GetHelper()
{
    std::string str;
    str.append(PC_TOOL_HELP_MSG);
    return str;
}

int HeapSnapshotPcToolMain(const int argc, const char **argv)
{
    if (argc < 3) { // 3: at least have 3 arguments
        std::cerr << GetHelper();
        return -1;
    }

    int newArgc = argc;
    std::string filePath = argv[argc - 2];
    std::string outputPath = argv[argc - 1];
    newArgc--;
    JSRuntimeOptions runtimeOptions;

    EcmaVM *vm = JSNApi::CreateEcmaVM(runtimeOptions);
    if (vm == nullptr) {
        std::cerr << "Cannot Create vm" << std::endl;
        return -1;
    }
    LOG_ECMA(INFO) << "ark Decode: " << filePath;
    std::ifstream file(filePath, std::ios::binary);
    if (file.is_open()) {
        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        ChunkDecoder chunk(content);
        file.close();
        DFXJSNApi::GenerateHeapSnapshotByBinFile(vm, chunk.GetRawHeapInfoArgs(), outputPath);
    }
    JSNApi::DestroyJSVM(vm);
    LOG_ECMA(INFO) << "ark HeapSnapshotPcToolMain finished";
    return 0;
}
}  // namespace panda::ecmascript


int main([[maybe_unused]] int argc, [[maybe_unused]] char const *argv[])
{
    return panda::ecmascript::HeapSnapshotPcToolMain(argc, argv);
}
